<?php

/*

Desktopd Wallpaper
Copyright (C) 2015  Desktopd Developer(s)

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

https://www.gnu.org/licenses/agpl.html

*/


namespace Desktopd\Wallpaper;

use Desktopd\PlatformTools\Random;
use Desktopd\PlatformTools\Loggable;
use Desktopd\PlatformTools\Logger;

use Desktopd\Colors\PixelWalk;


class WallpaperStore implements Loggable, \IteratorAggregate, \Countable {
    protected $dir;
    protected $sources = array();
    protected $pictures = array();
    
    protected $loggers = array();
    protected $refreshed = 0;
    protected $ttl;
    
    protected $color;
    protected $colorCount = 0;
    
    
    public function __construct ($dir, $ttl = 1800) {
        $this->ttl = max(1, (int) $ttl);
        
        $dir = "$dir";
        if ('' === $dir) {
            throw new \RuntimeException('Invalid directory');
        }
        
        if (!is_dir($dir)) {
            @mkdir($dir, 0755, true);
        }
        
        if (!is_dir($dir)) {
            throw new \RuntimeException("Could not create directory: $dir");
        }
        
        if (!is_writable($dir)) {
            throw new \RuntimeException("Non-writable directory: $dir");
        }
        
        $this->dir = $dir;
        
        $bookmarked = "$dir/bookmarked";
        $this->addSource($bookmarked);
        
        if (!is_dir($bookmarked)) {
            @mkdir($bookmarked, 0755, true);
        }
        
        $this->color = new PixelWalk();
        
        $colors = "$dir/colors";
        //$this->addSource($colors); // this will be duplicate
        
        if (!is_dir($colors)) {
            @mkdir($colors, 0755, true);
        }
        
        $old = glob("$colors/*");
        if (is_array($old)) {
            foreach ($old as $path) {
                if (!is_file($path)) {
                    continue;
                }
                
                unlink($path);
            }
        }
    }
    
    public function refresh () {
        $this->log("Refreshing the list of available pictures", Logger::INFO);
        $this->pictures = array();
        foreach ($this->sources as $source) {
            if ('' === $source) {
                continue;
            }
            
            if (!is_dir($source)) {
                continue;
            }
            
            foreach (glob("$source/*") as $path) {
                $name = basename($path);
                if (1 < count(explode('.', $name))) {
                    continue; // not a supported image
                }
                
                if (!is_file($path)) {
                    continue;
                }
                
                $this->pictures[] = $path;
            }
        }
        
        $this->refreshed = time();
    }
    
    public function addSource ($dir) {
        if (!in_array($dir, $this->sources, true)) {
            $this->sources[] = "$dir";
            $this->refresh();
        }
    }
    
    protected function generateColor () {
        $this->colorCount++;
        $luv = $this->color->CIELUV();
        $title = (string) $luv;
        
        // This should not conflict with other schemes (such as SHA256 in hex)
        $filename = sprintf(
            '%d_%+.4F,%+.4F,%+.4F'
            , $this->colorCount
            , $luv->getL()
            , $luv->getU()
            , $luv->getV()
        );
        
        $path = "{$this->dir}/colors/{$filename}";
        $debug = $this->color->generate($path);
        $metadata = array(
            'title' => "{$title}",
            'description' => "16-bit sRGB approximation of {$this->color}"
        );
        
        $flags = 0;
        if (defined('JSON_UNESCAPED_SLASHES')) {
            $flags |= JSON_UNESCAPED_SLASHES;
        }
        file_put_contents(
            "{$this->dir}/colors/{$filename}.json"
            , json_encode($metadata, $flags)
        );
        
        if (false === $debug) {
            $this->log("Failed to generate a 1x1 image", Logger::ERROR);
            return false;
        } else {
            $this->log("{$this->color} -> $debug", Logger::INFO);
            return $path;
        }
    }
    
    protected function getRandomPicture () {
        if (time() - $this->refreshed > $this->ttl) {
            $this->refresh();
        }
        
        $count = count($this->pictures);
        if (1 > $count) {
            $this->log("Empty list given", Logger::NOTICE);
            return false;
        }
        
        $key = Random::rand(0, $count - 1);
        return isset($this->pictures[$key]) ? $this->pictures[$key] : false;
    }
    
    public function change ($colorProb = 0.25) {
        $isColor = Random::prob($colorProb);
        try {
            if (!$isColor) {
                throw new \RuntimeException();
            }
            
            $picture = $this->generateColor();
            if (false === $picture) {
                throw new \RuntimeException();
            }
            
        } catch (\Exception $e) {
            $picture = $this->getRandomPicture();
            
            if (!file_exists($picture)) {
                $this->refresh();
                
                $picture = $this->getRandomPicture();
                if (false === $picture) {
                    $this->log("Picture not available!", Logger::NOTICE);
                    return false;
                }
                
                if (!file_exists($picture)) {
                    $this->log("Broken database!", Logger::WARNING);
                    return false;
                }
            }
            
        }
        
        
        copy($picture, "{$this->dir}/wallpaper");
        
        $metadata = "$picture.json";
        $title = false;//'(unknown)';
        if (is_file($metadata)) {
            copy($metadata, "{$this->dir}/metadata.json");
            $metadata = file_get_contents($metadata);
            if (false !== $metadata) {
                $metadata = json_decode($metadata, JSON_OBJECT_AS_ARRAY);
                if (isset($metadata['title'])) {
                    $title = "{$metadata['title']}";
                }
                
                if (is_array($metadata)) {
                    $str = "";
                    foreach ($metadata as $name => $value) {
                        switch ($name) {
                            case 'uri': $name = "URI"; break;
                            case 'api': $name = "Source"; break;
                        }
                        $name = ucfirst($name);
                        if (is_array($value)) {
                            $value = implode(' | ', $value);
                        }
                        $str .= "$name: $value" . PHP_EOL;
                    }
                    file_put_contents("{$this->dir}/metadata", $str);
                    $this->log("Metadata available at: {$this->dir}/metadata", Logger::INFO);
                }
            }
        }
        
        $this->log("Wallpaper changed to: $picture", Logger::INFO);
        if (false !== $title) {
            $this->log("Title: $title", Logger::INFO);
        }
        
        $escaped = var_export($picture, true);
        $basename = basename($picture);
        $bookmarked = "{$this->dir}/bookmarked/$basename";
        $escapedBookmarked = var_export($bookmarked, true);
        $escapedTitle = var_export($title !== false ? $title : '(unknown)', true);
        $bookmarker = <<<EOF
#!/usr/bin/env php
<?php
\$picture = $escaped;
\$bookmarked = $escapedBookmarked;
\$title = $escapedTitle;
\$force = false;
if (isset(\$argv[1])) {
    switch (\$argv[1]) {
        // May be used by scripts
        case '--about':
            echo \$title, PHP_EOL;
            exit(0);
            break;
        
        case '-f':
        case '--force':
            \$force = true;
            break;
        
    }
}

if (file_exists(\$bookmarked)) {
    echo "Already bookmarked", PHP_EOL;
    exit;
}

if (!\$force) {
    echo "Bookmark '\$title'?  [N/y]: ";
    \$isOK = strtolower(trim(fgets(STDIN)));
    if (\$isOK !== 'y' && \$isOK !== 'yes') {
        echo "Aborted!", PHP_EOL;
        exit(1);
    }
}

\$meta = "\$picture.json";
if (file_exists(\$meta)) {
    copy(\$meta, "\$bookmarked.json");
}
copy(\$picture, \$bookmarked);
echo "Bookmarked '\$title' to: \$bookmarked", PHP_EOL;

EOF;
        file_put_contents("{$this->dir}/add-bookmark.php", $bookmarker);
        chmod("{$this->dir}/add-bookmark.php", 0744);
        
        $dir = "{$this->dir}/dir";
        if (!is_dir($dir)) {
            @mkdir($dir, 0755, true);
        }
        
        if (!is_dir($dir)) {
            return false;
        }
        
        copy($picture, "$dir/wallpaper");
        
        return true;
    }
    
    public function getIterator () {
        return new \ArrayIterator($this->pictures);
    }
    
    public function count () {
        return count($this->pictures);
    }
    
    public function hasLogger (Logger $logger) {
        return in_array($logger, $this->loggers, true);
    }
    
    public function addLogger (Logger $logger) {
        if (!in_array($logger, $this->loggers, true)) {
            $this->loggers[] = $logger;
        }
        
        return true;
    }
    
    public function removeLogger (Logger $logger) {
        foreach (array_keys($this->loggers, $logger, true) as $key) {
            unset($this->loggers[$key]);
        }
        
        return true;
    }
    
    protected function log ($message, $level = Logger::INFO) {
        foreach ($this->loggers as $logger) {
            $logger->log($message, $level);
        }
    }
}


// vim: ts=4 et ai

